//
//  ZLLMachReport.m
//  ZLLCrashReportDemo
//
//  Created by 张亮亮 on 2018/3/30.
//  Copyright © 2018年 张亮亮. All rights reserved.
//

#import "ZLLMachReport.h"
#include <mach/mach.h>
#include <pthread.h>
#include <signal.h>

@implementation ZLLMachReport

@end

/** Our exception port. */
static mach_port_t g_exceptionPort = MACH_PORT_NULL;

/** Holds exception port info regarding the previously installed exception
 * handlers.
 */
static struct
{
    exception_mask_t        masks[EXC_TYPES_COUNT];
    exception_handler_t     ports[EXC_TYPES_COUNT];
    exception_behavior_t    behaviors[EXC_TYPES_COUNT];
    thread_state_flavor_t   flavors[EXC_TYPES_COUNT];
    mach_msg_type_number_t  count;
} g_previousExceptionPorts;

/** Primary exception handler thread. */
static pthread_t g_primaryPThread;
static thread_t g_primaryMachThread;
#define kThreadPrimary "KSCrash Exception Handler (Primary)"



/** Restore the original mach exception ports.
 */
static void restoreExceptionPorts(void)
{
  
    if(g_previousExceptionPorts.count == 0)
    {
        return;
    }
    
    const task_t thisTask = mach_task_self();
    kern_return_t kr;
    
    // Reinstall old exception ports.
    for(mach_msg_type_number_t i = 0; i < g_previousExceptionPorts.count; i++)
    {
     
        kr = task_set_exception_ports(thisTask,
                                      g_previousExceptionPorts.masks[i],
                                      g_previousExceptionPorts.ports[i],
                                      g_previousExceptionPorts.behaviors[i],
                                      g_previousExceptionPorts.flavors[i]);
        
    }
  
    g_previousExceptionPorts.count = 0;
}

/** Our exception handler thread routine.
 * Wait for an exception message, uninstall our exception port, record the
 * exception information, and write a report.
 */
static void * handleExceptions(void* const userData)
{
    // Exception handler – runs a message loop. Refactored into a standalone function
    // so as to allow easy insertion into a thread (can be in same program or different)
    mach_msg_return_t rc;
    // The exception message, straight from mach/exc.defs (following MIG processing) // copied here for ease of reference.
    typedef struct {
        mach_msg_header_t Head;
        /* start of the kernel processed data */
        mach_msg_body_t msgh_body;
        mach_msg_port_descriptor_t thread;
        mach_msg_port_descriptor_t task;
        /* end of the kernel processed data */
        NDR_record_t NDR;
        exception_type_t exception;
        mach_msg_type_number_t codeCnt;
        integer_t code[2];
        int flavor;
        mach_msg_type_number_t old_stateCnt;
        natural_t old_state[144];
    } Request;
    
    Request exc;
    
    struct rep_msg {
        mach_msg_header_t Head;
        NDR_record_t NDR;
        kern_return_t RetCode;
    } rep_msg;
    
    for(;;) {
        // Message Loop: Block indefinitely until we get a message, which has to be
        // 这里会阻塞，直到接收到exception message，或者线程被中断。
        // an exception message (nothing else arrives on an exception port)
        rc = mach_msg( &exc.Head,
                      MACH_RCV_MSG|MACH_RCV_LARGE,
                      0,
                      sizeof(Request),
                      g_exceptionPort, // Remember this was global – that's why.
                      MACH_MSG_TIMEOUT_NONE,
                      MACH_PORT_NULL);
        
        if(rc == MACH_MSG_SUCCESS) {
            /*... */
            break ;
        };
        
    }
    printf("Got message %d. Exception : %d Flavor: %d. Code %lld/%lld. State count is %d\\\\\\\\n" ,
           exc.Head.msgh_id, exc.exception, exc.flavor,
           exc.code[0], exc.code[1], // can also print as 64-bit quantity
           exc.old_stateCnt);
    
    restoreExceptionPorts();
    rep_msg.Head = exc.Head;
    rep_msg.NDR = exc.NDR;
    rep_msg.RetCode = KERN_FAILURE;
    // Normally we would call exc_server or other. In this example, however, we wish
    // to demonstrate the message contents:
   mach_msg(&rep_msg.Head,
              MACH_SEND_MSG,
              sizeof (rep_msg),
              0,
              MACH_PORT_NULL,
              MACH_MSG_TIMEOUT_NONE,
              MACH_PORT_NULL);
    
    return NULL;
}
static void uninstallExceptionHandler(void)
{
    
    // NOTE: Do not deallocate the exception port. If a secondary crash occurs
    // it will hang the process.
    
    restoreExceptionPorts();
    
    thread_t thread_self = mach_thread_self();
    mach_port_deallocate(mach_task_self(), thread_self);
    
    if(g_primaryPThread != 0 && g_primaryMachThread != thread_self)
    {
        
        //        if(g_isHandlingCrash)
        //        {
        thread_terminate(g_primaryMachThread);
        // }
        //        else
        //        {
        //            pthread_cancel(g_primaryPThread);
        //        }
        g_primaryMachThread = 0;
        g_primaryPThread = 0;
    }
    g_exceptionPort = MACH_PORT_NULL;
    //"Mach exception handlers uninstalled.");
}

 bool installMachHandler(void)
{
    //"Installing mach exception handler."
    kern_return_t kr;
    int error;
    
    const task_t thisTask = mach_task_self();
    // EXC_MASK_BAD_ACCESS  错误类型
    exception_mask_t mask = EXC_MASK_BAD_ACCESS |
    EXC_MASK_BAD_INSTRUCTION |
    EXC_MASK_ARITHMETIC |
    EXC_MASK_SOFTWARE |
    EXC_MASK_BREAKPOINT;
   
    //获取异常端口:Backing up original exception ports.
    kr = task_get_exception_ports(thisTask,
                                  mask,
                                  g_previousExceptionPorts.masks,
                                  &g_previousExceptionPorts.count,
                                  g_previousExceptionPorts.ports,
                                  g_previousExceptionPorts.behaviors,
                                  g_previousExceptionPorts.flavors);
    if(kr != KERN_SUCCESS)
    {
        //"task_get_exception_ports: %s", mach_error_string(kr)
        goto failed;
    }
    
    if(g_exceptionPort == MACH_PORT_NULL)
    {
       //Allocating new port with receive rights.
        kr = mach_port_allocate(thisTask,
                                MACH_PORT_RIGHT_RECEIVE,
                                &g_exceptionPort);
        if(kr != KERN_SUCCESS)
        {
           //KSLOG_ERROR("mach_port_allocate: %s", mach_error_string(kr));
            goto failed;
        }
        
      //Adding send rights to port.
        kr = mach_port_insert_right(thisTask,
                                    g_exceptionPort,
                                    g_exceptionPort,
                                    MACH_MSG_TYPE_MAKE_SEND);
        if(kr != KERN_SUCCESS)
        {
          //  "mach_port_insert_right: %s", mach_error_string(kr);
            goto failed;
        }
    }
    
   // "Installing port as exception handler."
    kr = task_set_exception_ports(thisTask,
                                  mask,
                                  g_exceptionPort,
                                  EXCEPTION_DEFAULT,
                                  THREAD_STATE_NONE);
    if(kr != KERN_SUCCESS)
    {
      //  "task_set_exception_ports: %s", mach_error_string(kr);
        goto failed;
    }
    //"Creating primary exception thread.");
    // at the end of catchMachExceptions, spawn the exception handling thread
    error = pthread_create(&g_primaryPThread,
                           NULL,
                           &handleExceptions,
                           kThreadPrimary);
//    if(error != 0)
//    {
//        //"pthread_create: %s", strerror(error));
//        goto failed;
//    }
//    pthread_attr_destroy(&attr);
//    g_primaryMachThread = pthread_mach_thread_np(g_primaryPThread);
//    ksmc_addReservedThread(g_primaryMachThread);
    
    //"Mach exception handler installed.");
    return true;
    
    
failed:
    //"Failed to install mach exception handler.");
    uninstallExceptionHandler();
    return false;
}

